import os
import django
import sys
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 导入根目录，并确保下面导入的包都在同一个层级
# print(base_dir)
sys.path.append(base_dir)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ywadmin.settings")
django.setup()

from rest_framework.pagination import PageNumberPagination
from ldap3 import Server, Connection, SAFE_SYNC, SUBTREE, MODIFY_REPLACE, HASHED_SALTED_SHA
from ldap3.core.exceptions import LDAPException, LDAPSocketOpenError
from ldap3.utils.hashed import hashed
from Crypto.Cipher import AES
import base64
import logging


logger = logging.getLogger('ywadmin')

class CommonPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 100

def get_object(model, **kwargs):
    if not kwargs:
        return None

    the_object = model.objects.filter(**kwargs)
    cnt = len(the_object)
    if cnt == 1:
        return the_object[0]
    elif cnt == 0:
        return None
    else:
        logger.error("查到多个值")
        raise Exception("查到多个值")


def print_tree_objects(nodes, indent=0):
    """递归打印树形对象结构"""
    for node in nodes:
        # 打印当前节点信息
        print("  " * indent + f"- {node.name} (id: {node.id}, parent_id: {node.parent_id})")
        # 如果有子节点，递归打印
        if hasattr(node, 'children') and node.children:
            print_tree_objects(node.children, indent + 1)

def build_tree(obj_list):
    """
    构建树形结构，支持多级菜单结构
    使用递归方式构建完整的树形结构
    """
    # 创建一个字典，将所有对象按ID索引
    obj_dict = {}
    for obj in obj_list:
        # 确保每个对象都有children属性
        if not hasattr(obj, 'children'):
            obj.children = []
        obj_dict[obj.id] = obj
    
    # 构建树形结构
    result_obj_list = []
    for obj in obj_list:
        # 如果是顶级对象，直接添加到结果列表
        if obj.parent_id == 0:
            result_obj_list.append(obj)
        else:
            # 将当前对象添加到其父对象的children列表中
            parent = obj_dict.get(obj.parent_id)
            if parent:
                parent.children.append(obj)
    
    return result_obj_list


def filter_button_menus(menu_tree):
    """
    递归过滤掉按钮类型的菜单项（menu_type='F'）
    用于前端菜单渲染，只保留目录(M)和菜单(C)类型
    
    :param menu_tree: 菜单树列表
    :return: 过滤后的菜单树
    """
    filtered_menus = []
    
    for menu in menu_tree:
        # 跳过按钮类型的菜单
        if hasattr(menu, 'menu_type') and menu.menu_type == 'F':
            continue
        
        # 如果有子菜单，递归过滤
        if hasattr(menu, 'children') and menu.children:
            menu.children = filter_button_menus(menu.children)
        
        filtered_menus.append(menu)
    
    return filtered_menus

def http_dns(domain):
    url = f"http://{settings.HTTP_DNS}/httpdns?domain={domain}"
    ret = requests.get(url, timeout=2)
    addr = ret.json()
    return addr

class LdapEntity(object):
    def __init__(self, ldap_config):
        self.ldap_config = ldap_config
        self.server = Server(ldap_config['server'], get_info=SAFE_SYNC)
        self.connection = Connection(self.server, user=ldap_config['admindn'], password=ldap_config['password'], \
                                auto_bind=True, version=3)

    def test_connection(self):
        try:
            # 执行搜索
            self.connection.search(self.ldap_config['basedn'], time_limit=self.ldap_config['timeout'], \
                                   search_filter=self.ldap_config['userFilter'], attributes=['sAMAccountName', 'mail'])
            return True, self.connection.entries
        except LDAPException as e:
            logger.error("LDAP连接测试失败,原因:%s" % str(e))
            return False, str(e)
    
    def sync_users(self, import_ou=None, sync_disabled=True, sync_existing=True):
        """
        从LDAP同步用户到本地数据库
        
        参数:
            import_ou: 导入用户的OU (如果为空，使用basedn)
            sync_disabled: 是否同步用户过期时间到本地 (True: 与LDAP服务器保持一致)
            sync_existing: 是否更新用户手机号 (True: 已导入的用户手机号将更新为LDAP服务器上的手机号信息)
        
        返回:
            (成功数, 失败数, 详细信息列表)
        """
        from system.models import SysUser
        
        success_count = 0
        fail_count = 0
        details = []
        
        try:
            # 确定搜索基础DN
            search_base = import_ou if import_ou else self.ldap_config['basedn']
            
            # 获取用户名属性和邮箱属性
            username_attr = self.ldap_config.get('userAttribute', 'sAMAccountName')
            email_attr = self.ldap_config.get('emailAttribute', 'mail')
            
            # 搜索用户，增加多种可能的用户属性
            search_attributes = [
                username_attr, email_attr, 
                'telephoneNumber', 'displayName', 'cn',
                'uid', 'sAMAccountName', 'userPrincipalName'  # 添加常见的用户名属性
            ]
            # 去重
            search_attributes = list(set(search_attributes))
            
            search_ok = self.connection.search(
                search_base=search_base,
                search_filter=self.ldap_config['userFilter'],
                search_scope=SUBTREE,
                attributes=search_attributes
            )
            
            if not search_ok:
                return 0, 0, [{'error': 'LDAP搜索失败'}]
            
            # 遍历所有用户
            for entry in self.connection.entries:
                try:
                    # 获取用户属性
                    entry_dict = entry.entry_attributes_as_dict
                    
                    # 获取用户名 - 尝试多个可能的属性
                    username = None
                    # 首先尝试配置的用户名属性
                    username_list = entry_dict.get(username_attr, [])
                    if username_list:
                        username = username_list[0] if isinstance(username_list, list) else username_list
                    
                    # 如果失败，尝试常见的用户名属性
                    if not username:
                        for attr in ['uid', 'cn', 'sAMAccountName', 'userPrincipalName']:
                            username_list = entry_dict.get(attr, [])
                            if username_list:
                                username = username_list[0] if isinstance(username_list, list) else username_list
                                break
                    
                    # 如果还是失败，尝试从DN中提取
                    if not username:
                        # DN格式通常是: uid=xxx,ou=xxx,dc=xxx 或 cn=xxx,ou=xxx,dc=xxx
                        dn_parts = entry.entry_dn.split(',')[0].split('=')
                        if len(dn_parts) == 2:
                            username = dn_parts[1]
                    
                    if not username:
                        fail_count += 1
                        details.append({'error': f'用户 {entry.entry_dn} 无法获取用户名'})
                        continue
                    
                    # 跳过admin用户
                    if username == 'admin':
                        continue
                    
                    # 获取邮箱
                    email_list = entry_dict.get(email_attr, [''])
                    email = email_list[0] if isinstance(email_list, list) and email_list else ''
                    
                    # 获取电话号码
                    phone_list = entry_dict.get('telephoneNumber', [''])
                    phonenumber = phone_list[0] if isinstance(phone_list, list) and phone_list else ''
                    
                    # 获取显示名称（昵称）
                    display_name_list = entry_dict.get('displayName', entry_dict.get('cn', [username]))
                    nick_name = display_name_list[0] if isinstance(display_name_list, list) and display_name_list else username
                    
                    # 检查用户是否已存在
                    user_exists = SysUser.objects.filter(username=username).exists()
                    
                    if user_exists:
                        # 用户已存在
                        if sync_existing:
                            # 更新手机号
                            user = SysUser.objects.get(username=username)
                            if phonenumber:
                                user.phonenumber = phonenumber
                            user.email = email
                            user.nick_name = nick_name
                            user.save()
                            success_count += 1
                            details.append({'username': username, 'action': '更新', 'status': '成功'})
                        else:
                            details.append({'username': username, 'action': '跳过', 'status': '已存在'})
                    else:
                        # 创建新用户
                        from django.contrib.auth.hashers import make_password
                        user = SysUser.objects.create(
                            username=username,
                            nick_name=nick_name,
                            email=email,
                            phonenumber=phonenumber,
                            password=make_password('YGreTh59cIPN'),  # 默认密码
                            user_from=2,
                            is_active=True if sync_disabled else True  # 根据sync_disabled参数设置
                        )
                        success_count += 1
                        details.append({'username': username, 'action': '创建', 'status': '成功'})
                        
                except Exception as e:
                    fail_count += 1
                    details.append({'error': f'处理用户失败: {str(e)}'})
                    logger.error(f"同步用户失败: {str(e)}")
            
            return success_count, fail_count, details
            
        except LDAPException as e:
            logger.error(f"LDAP同步用户失败: {str(e)}")
            return 0, 0, [{'error': f'LDAP错误: {str(e)}'}]

    def fetch_user_dn(self, uid):
        """
        根据用户名搜索用户信息，返回dn信息
        :return:
        "cn=zbx_admin,ou=admin,dc=dtzhejiang,dc=com"
        """
        searchFilter = '(uid=%s)' % uid
        self.connection.search(search_base=self.ldap_config['basedn'], search_scope=SUBTREE, search_filter=searchFilter)
        for item in self.connection.entries:
            return item.entry_dn

    def check_username_password(self, uid, password):
        """
        校验用户名 及密码
        :return:
        """
        user_dn = self.fetch_user_dn(uid)
        try:
            _c = Connection(self.server, user=user_dn, password=password)
            if _c.bind():
                logger.info("用户名密码校验成功")
                return True
            else:
                logger.error("用户名密码校验失败,原因:%s" % _c.result)
                return False
        except LDAPException as e:
            logger.error("用户名密码校验失败,原因:%s" % str(e))
            return False, str(e)

    def fetch_user_info(self, uid, attributes=['cn', 'sn', 'mail', 'telephoneNumber']):
        """
        根据用户ID获取用户信息
        :param uid: 用户名
        :param attributes: 要检索的属性列表
        :return: 包含用户属性的字典，如果未找到则返回空字典
        """
        user_dn = self.fetch_user_dn(uid)
        if not user_dn:
            logger.warning(f"获取用户信息失败：未找到用户 '{uid}' 的DN。")
            return {}

        # search_scope=BASE 表示只在提供的 search_base 上搜索，因为我们已经有了用户的DN
        search_ok = self.connection.search(
            search_base=user_dn,
            search_filter='(objectClass=*)',  # 过滤条件，(objectClass=*)表示匹配所有对象
            search_scope='BASE',
            attributes=attributes
        )

        if not (search_ok and self.connection.entries):
            logger.warning(f"LDAP search for user '{uid}' with DN '{user_dn}' returned no entries.")
            return {}

        # self.connection.entries 是一个包含搜索结果的列表
        user_info_raw = self.connection.entries[0].entry_attributes_as_dict
        user_data = {}

        # 将所有属性值从列表转换为单个值（如果适用）
        for attr_name, values in user_info_raw.items():
            user_data[attr_name] = values[0] if len(values) == 1 else values

        # 添加DN并返回
        user_data['dn'] = user_dn
        return user_data

    def reset_password(self, uid, password):
        """
        重置用户密码
        :return:
        """
        try:
            user_dn = self.fetch_user_dn(uid)
            hashed_password = hashed(HASHED_SALTED_SHA, password)
            self.connection.modify(user_dn, {'userPassword': [(MODIFY_REPLACE, hashed_password)]})
            return True, "密码重置成功"
        except LDAPException as e:
            logger.error("密码重置失败,原因:%s" % str(e))
            return False, str(e)

    def modify_password(self, uid, old_password, new_password):
        """
        修改用户密码
        :return:
        """
        try:
            user_dn = self.fetch_user_dn(uid)
            if not self.check_username_password(uid, old_password):
                return False, "旧密码错误"
            hashed_password = hashed(HASHED_SALTED_SHA, new_password)
            self.connection.modify(user_dn, {'userPassword': [(MODIFY_REPLACE, hashed_password)]})
            return True, "密码修改成功"
        except LDAPException as e:
            logger.error("密码修改失败,原因:%s" % str(e))
            return False, str(e)
    
    def get_ou_list(self):
        """
        获取LDAP服务器的OU列表
        
        返回:
            OU列表
        """
        try:
            # 搜索所有组织单元
            search_ok = self.connection.search(
                search_base=self.ldap_config['basedn'],
                search_filter='(objectClass=organizationalUnit)',
                search_scope=SUBTREE,
                attributes=['ou', 'distinguishedName']
            )
            
            if not search_ok:
                return []
            
            ou_list = []
            for entry in self.connection.entries:
                entry_dict = entry.entry_attributes_as_dict
                ou_name = entry_dict.get('ou', [None])[0]
                if ou_name:
                    ou_list.append({
                        'name': ou_name,
                        'dn': entry.entry_dn
                    })
            
            return ou_list
            
        except LDAPException as e:
            logger.error(f"获取OU列表失败: {str(e)}")
            return []

class PyCrypt(object):
    def __init__(self, key=None):
        self.key = "2155ca7p906af5e1"
        # self.iv = b"8155ca7d906ad5e1"  # 偏移量可选参数

    # 需要补位，str不是16的倍数那就补足为16的倍数
    def add_to_16(self, value):
        while len(value) % 16 != 0:
            value += '\0'
        return str.encode(value)  # 返回bytes

    # 加密方法
    def encrypt(self, text):
        aes = AES.new(self.add_to_16(self.key), AES.MODE_ECB)  # 初始化加密器
        encrypt_aes = aes.encrypt(self.add_to_16(text))  # 先进行aes加密
        encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8')  # 执行加密并转码返回bytes
        return encrypted_text

    # 解密方法
    def decrypt(self, text):
        aes = AES.new(self.add_to_16(self.key), AES.MODE_ECB)  # 初始化加密器
        base64_decrypted = base64.decodebytes(text.encode(encoding='utf-8'))  # 优先逆向解密base64成bytes
        decrypted_text = str(aes.decrypt(base64_decrypted), encoding='utf-8').replace('\0', '')  # 执行解密密并转码返回str
        # 去掉后面的空字符
        decrypted_text_nans = decrypted_text.rstrip('\0')
        return decrypted_text_nans


if __name__ == '__main__':
    p = PyCrypt()
    # r1 = p.encrypt("AZcBSXofiQArhyYbDnv9iAFipP5pQN")
    # print(r1)
    # r1="xzsb6sxBU8GaHNKgGGaurBNAyZr8fNDpZGcVHdirO0s="
    # print(p.decrypt(r1))
    ldap_config = {
        "server": "ldap://59.202.43.66:389",
        # "server": "ldap://127.0.0.1:389",
        "basedn": "dc=dtzhejiang,dc=com",
        "timeout": 10,
        "admindn": "cn=admin,dc=dtzhejiang,dc=com",
        "password": "28c1xzz6AI5z",
        "userFilter": "(objectClass=user)",
        "userAttribute": "sAMAccountName"
    }
    ldap = LdapEntity(ldap_config)
    # print(ldap.test_connection())
    # print(ldap.fetch_user_dn('zhugaotao'))
    # print(ldap.check_username_password('zgt', '20070522'))
    print(ldap.reset_password('zgt', '20070522'))

